package io.wongxd.solution.compose.custom.modalsheet

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.R
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.ViewRootForInspector
import androidx.compose.ui.semantics.popup
import androidx.compose.ui.semantics.semantics
import androidx.core.view.children
import androidx.lifecycle.*
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import java.util.UUID

/**
 * Opens a popup with the given content.
 * The popup is visible as long as it is part of the composition hierarchy.
 *
 * Note: This is highly reduced version of the official Popup composable with some changes:
 * * Fixes an issue with action mode (copy-paste) menu, see https://issuetracker.google.com/issues/216662636
 * * Adds the view to the decor view of the window, instead of the window itself.
 * * Do not have properties, as Popup is laid out as fullscreen.
 *
 * @param onDismiss Executes when the user clicks outside of the popup.
 * @param content The content to be displayed inside the popup.
 */
@Composable
fun WFullscreenPopup(
    onDismiss: (() -> Unit)? = null,
    content: @Composable () -> Unit
) {
    val view = LocalView.current
    val parentComposition = rememberCompositionContext()
    val currentContent by rememberUpdatedState(content)
    val popupId = rememberSaveable { UUID.randomUUID() }
    val popupLayout = remember {
        WPopupLayout(
            onDismiss = onDismiss,
            composeView = view,
            popupId = popupId
        ).apply {
            setContent(parentComposition) {
                Box(Modifier.semantics { this.popup() }) {
                    currentContent()
                }
            }
        }
    }

    DisposableEffect(popupLayout) {
        popupLayout.show()
        popupLayout.updateParameters(
            onDismiss = onDismiss
        )
        onDispose {
            popupLayout.disposeComposition()
            // Remove the window
            popupLayout.dismiss()
        }
    }

    SideEffect {
        popupLayout.updateParameters(
            onDismiss = onDismiss
        )
    }
}

/**
 * The layout the popup uses to display its content.
 */
@SuppressLint("ViewConstructor")
class WPopupLayout(
    private var onDismiss: (() -> Unit)?,
    composeView: View,
    popupId: UUID
) : AbstractComposeView(composeView.context),
    ViewRootForInspector {

    private val decorView = findOwner<Activity>(composeView.context)?.window?.decorView as ViewGroup

    override val subCompositionView: AbstractComposeView get() = this

    /**
     * Set the [LifecycleOwner] responsible for managing the given [View].
     * Calls to [get] from this view or descendants will return `lifecycleOwner`.
     *
     * This should only be called by constructs such as activities or fragments that manage
     * a view tree and reflect their own lifecycle through a [LifecycleOwner]. Callers
     * should only set a [LifecycleOwner] that will be *stable.* The associated
     * lifecycle should report that it is destroyed if the view tree is removed and is not
     * guaranteed to later become reattached to a window.
     *
     * @param lifecycleOwner LifecycleOwner representing the manager of the given view
     */
    @JvmName("set")
    fun View.setViewTreeLifecycleOwner(lifecycleOwner: LifecycleOwner?) {
        setTag(io.wongxd.solution.R.id.view_tree_lifecycle_owner, lifecycleOwner)
    }

    /**
     * Set the [ViewModelStoreOwner] associated with the given [View].
     * Calls to [get] from this view or descendants will return
     * `viewModelStoreOwner`.
     *
     * This should only be called by constructs such as activities or fragments that manage
     * a view tree and retain state through a [ViewModelStoreOwner]. Callers
     * should only set a [ViewModelStoreOwner] that will be *stable.* The associated
     * [ViewModelStore] should be cleared if the view tree is removed and is not
     * guaranteed to later become reattached to a window.
     *
     * @param viewModelStoreOwner ViewModelStoreOwner associated with the given view
     */
    @JvmName("set")
    fun View.setViewTreeViewModelStoreOwner(viewModelStoreOwner: ViewModelStoreOwner?) {
        setTag(io.wongxd.solution.R.id.view_tree_view_model_store_owner, viewModelStoreOwner)
    }

    /**
     * Set the [SavedStateRegistryOwner] responsible for managing the saved state for this [View]
     * Calls to [get] from this view or descendants will return `owner`.
     *
     * This is is automatically set for you in the common cases of using fragments or
     * ComponentActivity.
     *
     *
     * This should only be called by constructs such as activities or fragments that manage
     * a view tree and their saved state through a [SavedStateRegistryOwner]. Callers
     * should only set a [SavedStateRegistryOwner] that will be *stable.* The
     * associated [SavedStateRegistry] should be cleared if the view tree is removed and is
     * not guaranteed to later become reattached to a window.
     *
     * @param owner The [SavedStateRegistryOwner] responsible for managing the
     * saved state for the given view
     */
    @JvmName("set")
    fun View.setViewTreeSavedStateRegistryOwner(owner: SavedStateRegistryOwner?) {
        setTag(io.wongxd.solution.R.id.view_tree_saved_state_registry_owner, owner)
    }

    init {
        id = android.R.id.content
        setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner())
        setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner())
        setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
        // Set unique id for AbstractComposeView. This allows state restoration for the state
        // defined inside the Popup via rememberSaveable()
        setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId")
        setTag(io.wongxd.solution.R.id.consume_window_insets_tag, false)
    }

    private var content: @Composable () -> Unit by mutableStateOf({})

    override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
        private set

    fun show() {
        // Place popup above all current views
        z = decorView.children.maxOf { it.z } + 1
        decorView.addView(
            this,
            0,
            MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        )

        requestFocus()
    }

    fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
        setParentCompositionContext(parent)
        this.content = content
        shouldCreateCompositionOnAttachedToWindow = true
    }

    @Composable
    override fun Content() {
        content()
    }

    @Suppress("ReturnCount")
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK && onDismiss != null) {
            if (keyDispatcherState == null) {
                return super.dispatchKeyEvent(event)
            }
            if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
                val state = keyDispatcherState
                state?.startTracking(event, this)
                return true
            } else if (event.action == KeyEvent.ACTION_UP) {
                val state = keyDispatcherState
                if (state != null && state.isTracking(event) && !event.isCanceled) {
                    onDismiss?.invoke()
                    return true
                }
            }
        }
        return super.dispatchKeyEvent(event)
    }

    fun updateParameters(
        onDismiss: (() -> Unit)?
    ) {
        this.onDismiss = onDismiss
    }

    fun dismiss() {
        setViewTreeLifecycleOwner(null)
        decorView.removeView(this)
    }
}

private inline fun <reified T> findOwner(context: Context): T? {
    var innerContext = context
    while (innerContext is ContextWrapper) {
        if (innerContext is T) {
            return innerContext
        }
        innerContext = innerContext.baseContext
    }
    return null
}
